package pc

import (
	. "chess_platform/internal/common"
	"encoding/json"
	"fmt"
	"github.com/astaxie/beego/orm"
)

const (
	RoleMenuKey = "PcRoleMenuKey-"
	AllMenuKey  = "PcMenuKey"
)

func RoleInfoById(id int64, multiOrm ...orm.Ormer) (*Role, error) {
	sql := fmt.Sprintf(`
		SELECT
			*
		FROM
			%v
		WHERE
			id = ?
		LIMIT 1
		`, TablePcRole)
	var role Role
	o := NewOrm(multiOrm, DBChessCenter)
	err := o.Raw(sql, id).QueryRow(&role)
	if CheckNoExist(err) {
		return nil, nil
	}
	return &role, err
}

func IsRoleExistByName(name string, multiOrm ...orm.Ormer) (*Role, bool) {
	sql := fmt.Sprintf(`
		SELECT
			id,mutable
		FROM
			%v
		WHERE
			name = ?
		LIMIT 1
		`, TablePcRole)
	o := NewOrm(multiOrm, DBChessCenter)
	var role Role
	err := o.Raw(sql, name).QueryRow(&role)
	if CheckNoExist(err) {
		return nil, false
	}
	return &role, true
}

func IsRoleExistById(id int64, multiOrm ...orm.Ormer) (*Role, bool) {
	sql := fmt.Sprintf(`
		SELECT
			id,mutable
		FROM
			%v
		WHERE
			id = ?
		LIMIT 1
		`, TablePcRole)
	o := NewOrm(multiOrm, DBChessCenter)
	var role Role
	err := o.Raw(sql, id).QueryRow(&role)
	if CheckNoExist(err) {
		return nil, false
	}
	return &role, true
}

// delete role
func DeleteRole(id int64, multiOrm ...orm.Ormer) error {
	o := NewOrm(multiOrm, DBChessCenter)
	sql := fmt.Sprintf(`
		SELECT
			uid
		FROM
			%v
		WHERE
			role_id = %v
		LIMIT 1
	`, TablePcUser, id)
	var uid int64
	err := o.Raw(sql).QueryRow(&uid)
	if CheckNoExist(err) { // no user binding role
		condition := fmt.Sprintf("id=%v", id)
		sql := DeleteSql(TablePcRole, condition)
		_, err := o.Raw(sql).Exec()
		return err
	}
	//has been binding
	return MsgErrRoleAlreadyBind
}

func NewRole(role map[string]interface{}) (int64, error) {
	return newRole(role)
}

// 更新角色名及角色名下的权限
func UpdateRole(role map[string]interface{}, roleId int64) error {
	return updateRole(role, roleId)
}

//获取角色列表
func ListRole(p ListParams, name string, isSuperAdmin bool, multiOrm ...orm.Ormer) ([]Role, int64, error) {
	limit := (p.PageIndex - 1) * p.PageSize
	where := ` 1=1 `
	if name != "" {
		where = where + fmt.Sprintf(" AND name LIKE '%v' ", "%"+name+"%")
	}
	if !isSuperAdmin { //不是超级管理员，内置角色不能展示
		where = where + fmt.Sprintf(" AND mutable = 1")
	}

	sql := fmt.Sprintf(`
		SELECT
			id,name,create_time,mutable
		FROM
			%v
		WHERE
			%v
		LIMIT ?,?
		`, TablePcRole, where)
	o := NewOrm(multiOrm, DBChessCenter)
	var role []Role
	_, err := o.Raw(sql, limit, p.PageSize).QueryRows(&role)
	if err != nil {
		return nil, 0, err
	}
	sql = fmt.Sprintf(`SELECT COUNT(1) FROM %v WHERE %v `, TablePcRole, where)
	var count int64
	err = o.Raw(sql).QueryRow(&count)
	return role, count, err
}

//获取此角色的已授权权限信息
//TODO:要做缓存
func RoleAuthorizedInfo(roleId int64, isSuperAdmin bool, multiOrm ...orm.Ormer) (*Role, error) {
	o := NewOrm(multiOrm, DBChessCenter)
	role, err := RoleInfoById(roleId, o)
	if err != nil {
		return nil, err
	}
	if role == nil {
		return nil, nil
	}

	parent, child, err := ListMenu(o)
	if err != nil {
		return role, err
	}
	if isSuperAdmin {
		for _, v := range parent {
			v.Selected = 1
		}
		for _, v := range child {
			v.Selected = 1
		}
	} else {
		var listMenu []int64
		err = json.Unmarshal([]byte(role.Menu), &listMenu)
		if err != nil {
			err = fmt.Errorf(fmt.Sprintf("角色(ID:%v)的权限错误:%v", role.Id, err))
			LogPc.Error(err.Error())
			return role, MsgRoleNoPermission
		}

		//efficient way
		childMap := make(map[int64]*Menu, 0)
		for _, c := range child {
			childMap[c.Id] = c
		}
		parentMap := make(map[int64]*Menu, 0)
		for _, c := range parent {
			parentMap[c.Id] = c
		}

		for _, v := range listMenu {
			_, ok := childMap[v]
			if ok {
				childMap[v].Selected = 1
			} else { //这权限不可能同时在父节点和子节点,所以就用if else
				_, ok1 := parentMap[v]
				if ok1 {
					parentMap[v].Selected = 1
				}
			}
		}
	}
	role.MenuList = MergeMenu(parent, child)
	return role, nil
}

//筛选出这个角色没有权限(全部是平级的，没有层次的)
func SimplifyPermission(role *Role) []*Menu {
	var menu []*Menu
	m := new(Menu)
	for _, v := range role.MenuList {
		if v.Selected == 1 {
			m = &Menu{
				Id:         v.Id,
				ParentId:   v.ParentId,
				Name:       v.Name,
				ModuleName: v.ModuleName,
				Url:        v.Url,
				Selected:   1,
			}
			//menu = append(menu, m)
			for _, vv := range v.Child {
				if vv.Selected == 1 {
					mm := &Menu{
						Id:         vv.Id,
						ParentId:   vv.ParentId,
						Name:       vv.Name,
						ModuleName: vv.ModuleName,
						Url:        vv.Url,
						Selected:   1,
					}
					m.Child = append(m.Child, mm)
					//menu = append(menu, mm)
				}
			}
			menu = append(menu, m)
		}
	}
	return menu
}

//校验此角色访问此uri是否有权限
//TODO:要做缓存
func CheckAccess(parentModule, childModule string, roleId int64) bool {
	role, err := RoleAuthorizedInfo(roleId, false)
	if err != nil {
		LogPc.Error(err.Error())
		return false
	}
	parent := make(map[string]string, 0)
	child := make(map[string]string, 0)
	for _, v := range SimplifyPermission(role) {
		parent[v.ModuleName] = v.ModuleName
		for _, vv := range v.Child {
			child[vv.ModuleName] = v.ModuleName
		}
	}
	_, ok := parent[parentModule]
	if !ok {
		LogPc.Debug(fmt.Sprintf("访问父模块:%v,无权限", parentModule))
		return false
	}

	_, ok = child[childModule]
	if !ok {
		LogPc.Debug(fmt.Sprintf("访问子模块:%v,无权限", childModule))
		return false
	}

	LogPc.Debug(fmt.Sprintf("访问父模块:%v,子模块:%v,有权限", parentModule, childModule))
	return true
}

//更新把此角色对应的menu更新到cache里
func UpdateRoleMenuToCache(roleId int64) {
	roleKey := fmt.Sprintf("%v%v", RoleMenuKey, roleId)
	menu, err := roleMenu(roleId)
	if err != nil {
		LogPc.Error(err.Error())
	}
	SetCache(MemCache, roleKey, menu, CacheExpire) //one day

}

func newRole(role map[string]interface{}, multiOrm ...orm.Ormer) (int64, error) {
	o := NewOrm(multiOrm, DBChessCenter)
	values, sql := InsertSql(TablePcRole, role)
	result, err := o.Raw(sql, values).Exec()
	if err != nil {
		return 0, err
	}
	return result.LastInsertId()
}

func updateRole(role map[string]interface{}, roleId int64, multiOrm ...orm.Ormer) error {
	o := NewOrm(multiOrm, DBChessCenter)
	condition := fmt.Sprintf(`id = %v`, roleId)
	values, sql := UpdateSql(TablePcRole, role, condition)
	_, err := o.Raw(sql, values).Exec()
	return err
}
